#ifndef __DALELIB_M4_H__
#define __DALELIB_M4_H__

#include <math.h>
#include <dalelib/maths/v3.h>
#include <dalelib/maths/v4.h>
#include <dalelib/maths/m3.h>

namespace dalelib
{

class m4
{
private:

	float f[16];

public:

	m4() { }
	m4( const float* _Ptr );
	m4( const float _Value00, const float _Value01, const float _Value02, const float _Value03, const float _Value04, const float _Value05, const float _Value06, const float _Value07, const float _Value08, const float _Value09, const float _Value10, const float _Value11, const float _Value12, const float _Value13, const float _Value14, const float _Value15 );
    m4( const v4& _Diagonal );
	m4( const v4& _Value0, const v4& _Value1, const v4& _Value2, const v4& _Value3 );
    m4( const m3& _Rot, const v3& _Pos );
    m4( const m4& _Mat );

	static m4 const zero;
	static m4 const identity;

	static m4 translation(const v3& _Vec) { return m4(1,0,0,0, 0,1,0,0, 0,0,1,0, _Vec[0],_Vec[1],_Vec[2],1); }
	static m4 scaling(const v3& _Scl) { return m4(_Scl[0],0,0,0, 0,_Scl[1],0,0, 0,0,_Scl[2],0, 0,0,0,1); }
	static m4 rotation(const v2& _Ang) // polar coordinates
	{
		float stheta = sinf(_Ang[0]), sphi = sinf(_Ang[1]);
		float ctheta = cosf(_Ang[0]), cphi = cosf(_Ang[1]);
		return m4(
			 ctheta*cphi,        stheta*cphi,       -sphi,       0,
			-stheta,             ctheta,             0,          0,
			 ctheta*sphi,        stheta*sphi,        cphi,       0,
			 0,                  0,                  0,          1 );
	}
	static m4 rotation(const v3& _Ang) // euler coordinates
	{
		float sx = sinf(_Ang[0]),sy = sinf(_Ang[1]), sz = sinf(_Ang[2]);
		float cx = cosf(_Ang[0]),cy = cosf(_Ang[1]), cz = cosf(_Ang[2]);
		return m4(
			cz*cy,              sz*cy,             -sy,        0,
			cz*sy*sx-sz*cx,     sz*sy*sx+cz*cx,     cy*sx,     0,
			cz*sy*cx+sz*sx,     sz*sy*cx-cz*sx,     cy*cx,     0,
			0,                  0,                  0,         1 );
	}
	static m4 rotation(const v3& _Axis,const float _Angle) // axis angle
	{
		float s = sinf(_Angle), c = cosf(_Angle), t = 1.f-c;
		return m4(
			t*_Axis[0]*_Axis[0]+c,			t*_Axis[0]*_Axis[1]+s*_Axis[2],	t*_Axis[0]*_Axis[2]-s*_Axis[1],	0,
			t*_Axis[0]*_Axis[1]-s*_Axis[2],	t*_Axis[1]*_Axis[1]+c,			t*_Axis[1]*_Axis[2]+s*_Axis[0],	0,
			t*_Axis[0]*_Axis[2]+s*_Axis[1],	t*_Axis[1]*_Axis[2]-s*_Axis[0],	t*_Axis[2]*_Axis[2]+c,			0,
			0, 								0,					 			0,				 				1 );
	}
	static m4 rotationX(const float _Angle)
	{
		const float sa = sinf(_Angle), ca = cosf(_Angle);
		return m4( 1,0,0,0,    0,ca,-sa,0,    0,sa,ca,0,    0,0,0,1 );
	}
	static m4 rotationY(const float _Angle)
	{
		const float sa = sinf(_Angle), ca = cosf(_Angle);
		return m4( ca,0,-sa,0,    0,1,0,0,    sa,0,ca,0,    0,0,0,1 );
	}
	static m4 rotationZ(const float _Angle)
	{
		const float sa = sinf(_Angle), ca = cosf(_Angle);
		return m4( ca,-sa,0,0,    sa,ca,0,0,    0,0,1,0,    0,0,0,1);
	}

	const float& operator[](const int _Index) const { return f[_Index]; }
	      float& operator[](const int _Index)       { return f[_Index]; }

	const float* ptr() const { return f; }
	       float* ptr()       { return f; }
//	float& operator[]( const int _Index );
	const v4 x() const { return v4( f[ 0], f[ 1], f[ 2], f[ 3] ); }
//	v4 x() const { return v4( f[ 0], f[ 1], f[ 2], f[ 3] ); }
	const v4 y() const { return v4( f[ 4], f[ 5], f[ 6], f[ 7] ); }
//	v4 y() const { return v4( f[ 4], f[ 5], f[ 6], f[ 7] ); }
	const v4 z() const { return v4( f[ 8], f[ 9], f[10], f[11] ); }
//	v4 z() const { return v4( f[ 8], f[ 9], f[10], f[11] ); }
	const v4 w() const { return v4( f[12], f[13], f[14], f[15] ); }
//	v4 w() const { return v4( f[12], f[13], f[14], f[15] ); }
	const v3 getTranslationPart() const { return v3(f[12],f[13],f[14]); }
	      v3 getTranslationPart()       { return v3(f[12],f[13],f[14]); }
	const m3 getRotationPart() const { return m3(f[0],f[1],f[2], f[4],f[5],f[6], f[8],f[9],f[10]); }
	      m3 getRotationPart()       { return m3(f[0],f[1],f[2], f[4],f[5],f[6], f[8],f[9],f[10]); }

	m4 transpose() const;
	m4 fastInverse() const;
	m4 inverse() const;
	m4 noRotation() const;
	m4 noTranslation() const;


	m4 operator*( const float _Value ) const;
	v4 operator*( const v4& _Value ) const;
	m4 operator*( const m4& _Value ) const;
	m4 operator+(const m4& _Value) const;
	m4 operator-(const m4& _Value) const;
	void operator+=( const m4& _Value );
	void operator-=( const m4& _Value );

	v3 transformVector( const v3& _Value ) const;
	v3 transformPoint( const v3& _Value ) const;
	void lookAt(const v3& _Pos,const v3& _Tgt,const v3& _Up=v3(0,1,0));
	void lookAtUpright(const v3& _Pos,const v3& _Tgt);
	void print(const std::string _Str="") const;

	void reorthonormalise();

//	Ray createRayFromVector( const v3 _Value ) const;

};

} // namespace dalelib

#endif
